﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Etapa1.ModelDescription;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Etapa1.SubdivisionSurface.butterfly
{
    public class Butterfly
    {
        public Butterfly()
        {
            InitializeBlendingWeights();
        }

        float[][] vertBlendingWeights;
        const int MAX_VERTEX_VALENCE = 50;
        float w = 0;

        public float Weight
        {
            get { return w; }
            set { w = value; }
        }

        public void SortEdges(List<Vertice> vertices)
        {
          // Now the somewhat harder part -- sort them by angle.  Pick an arbitrary one to start
          // with.
          for (int x=0; x<vertices.Count; x++)
          {            

            // We'll use these.  A lot.
            Vector3 normal = vertices[x].VertexInfo.Normal;
            Vector3 vertex = vertices[x].VertexInfo.Position;            

            // Use this to sort them.
            SortedDictionary<float, Edge> sortedEdges = new SortedDictionary<float, Edge>();

            // Just start with the first one, all the others will be stored relative to it, in
            // CCW-wound order.
            Edge edge = vertices[x].EdgesIn[0];
            
            sortedEdges[0.0f] = edge;

            Vector3 refVector = edge.Other(vertices[x]).VertexInfo.Position;
            Vertice OtherVertice = edge.Other(vertices[x]);            
            refVector -= vertex;   //edgevector

            // Project the reference (angle == 0) vector into the tangent plane.
            Vector3 projVec = normal;            
            projVec *= Vector3.Dot(refVector,normal);
            refVector -= projVec;
            refVector.Normalize();

            //others edges that leaves this vertice
            for (int y=1; y<vertices[x].EdgesIn.Count; y++)
            {
             
              Edge edges = vertices[x].EdgesIn[y];

              // Find the vector from the vertex along this edge.
              Vector3 vertVector = edges.Other(vertices[x]).VertexInfo.Position;
              vertVector -= vertex;

              // Find the vector projected into the tangent plane.
              projVec = normal;
              projVec *= Vector3.Dot(vertVector,normal);
              vertVector -= projVec;
              vertVector.Normalize();

              // Find the angle between it and the refence vector.  
              // v1*v2 = |v1||v2|cos(theta) = cos(theta) 
              float cosTheta = Vector3.Dot(vertVector,refVector);
              if (cosTheta < -1.0f) cosTheta = -1.0f;
              if (cosTheta > 1.0f) cosTheta = 1.0f;
              float angle = (float)Math.Acos(cosTheta);
              angle *= 180.0f / (float) Math.PI; ///Degress :)

              // To find the sign, it's a clockwise (negative) angle if the cross product points 
              // away from the tangent plane normal, so map it properly.
              Vector3 crossProd;
              crossProd = Vector3.Cross(vertVector,refVector);
              if (Vector3.Dot(crossProd,normal) < 0)
              {
                angle = 360 - angle;
              }

              if (sortedEdges.ContainsKey(angle))
              {
                  angle += 0.01f;
                  while (sortedEdges.ContainsKey(angle))
                      angle += 0.01f;
                 
                  sortedEdges[angle] = edges;
              }
              else              
              {
                  sortedEdges[angle] = edges;
              }             

            }////SOME HOURS TO FIND THIS FUCKING SOLUTION !!!! - talk to yourself in the coments is perfectly normal ..., maybe ...

             //sortedEdges            
            System.Diagnostics.Debug.Assert(sortedEdges.Values.Count == vertices[x].EdgesIn.Count);

            // Okay, it's all sorted, put it into the vertex's edge list in the right order.     
            //foreach (var item in sortedEdges.Values)
            //{
            //    vertices[x].EdgesIn.Contains(item);
            //} LEMBRAR: debug only
            
            vertices[x].EdgesIn.Clear();
            foreach(Edge ed in sortedEdges.Values)
            {                
                vertices[x].EdgesIn.Add(ed);              
            }
          }
        }

        private void getContributionToVert(Vertice v0, Edge processingNow, ref Vector3 newPos  )
        {            
            int indexOf = v0.EdgesIn.IndexOf(processingNow);
            for (int i = 0; i < v0.EdgesIn.Count; i++)
            {
                int edgeNum = (i + indexOf) % v0.EdgesIn.Count;
                Edge e = v0.EdgesIn[edgeNum];
                Vector3 other = e.Other(v0).VertexInfo.Position;

                newPos += other*vertBlendingWeights[v0.EdgesIn.Count][i];                
            }

            if (v0.EdgesIn.Count != 6)
              {
                newPos += v0.VertexInfo.Position*0.75f;                
              }            
        }

        public void Tesselate(ref List<Edge> edges, ref List<Vertice> vertices,ref List<Triangulo> triangs)
        {
            Vector3[] newEdgePosition = new Vector3[edges.Count];
            bool[] edgeCanBeTesselated = new bool[edges.Count];

            int i = 0;
            foreach (var item in edges)
            {
                int valenciaV0 = item.V0.Valencia();
                int valenciaV1 = item.V1.Valencia();
                Vector3 newPos = Vector3.Zero;

                if (valenciaV0 < 3 || valenciaV1 < 3)
                {
                    edgeCanBeTesselated[i] = false;
                    ///CLEARLY, WE GOT A PROBLEM !!!!!!!                    
                    ///PSEUDOSOLUCAO: Tesselar diferente os triangulas das bordas
                }
                else
                {
                    edgeCanBeTesselated[i] = false;
                }                
                
                if (valenciaV0 == 6 && valenciaV1 == 6)
                {
                    getContributionToVert(item.V0, item, ref newPos);
                    getContributionToVert(item.V1, item, ref newPos);
                }
                else if (valenciaV0 == 6)
                {
                    getContributionToVert(item.V1, item, ref newPos);
                }
                else if (valenciaV1 == 6)
                {
                    getContributionToVert(item.V0, item, ref newPos);
                }
                else
                {
                    getContributionToVert(item.V0, item, ref newPos);
                    getContributionToVert(item.V1, item, ref newPos);
                    newPos *= 0.5f;
                }
                newEdgePosition[i++] = newPos;
            }

            int j = 0;
            List<Vertice> newVerticesEdges = new List<Vertice>();
            foreach (var item in edges)
            {
                if (edgeCanBeTesselated[j] == true)
                {
                    ///Pra TextureCoordinate OK
                    ///Pra normal NAO ROLA, fica zuado, PENSAR EM UMA SOLUCAO MELHOR 
                    Vector2 t0 = item.V0.VertexInfo.TextureCoordinate;
                    Vector2 t1 = item.V1.VertexInfo.TextureCoordinate;
                    Vector3 n0 = item.V0.VertexInfo.Normal;
                    Vector3 n1 = item.V1.VertexInfo.Normal;
                    VertexPositionNormalTexture ver = new VertexPositionNormalTexture(newEdgePosition[j], (n1 + n0) * .5f, (t1 + t0) * .5f);
                    Vertice v = new Vertice(ver, vertices.Count);
                    vertices.Add(v);
                    newVerticesEdges.Add(v);                
                }
                else
                {                    
                    newVerticesEdges.Add(null);
                }                
                j++;                
            }

            foreach (var item in vertices)
            {
                item.TriangulosIn.Clear();
                item.EdgesIn.Clear();
            }

            List<Triangulo> newTrianglesList = new List<Triangulo>();
            List<Edge> newEdgesList = new List<Edge>();
            foreach (var item in triangs)
            {

                if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == false
                    || edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == false
                    || edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == false)
                {
                    if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == false
                        && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == true
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == true)
                    {
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], item.Vertices[1], newVerticesEdges[item.Edges[1].PositionInEdgeList], newEdgesList));
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], newVerticesEdges[item.Edges[1].PositionInEdgeList], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                        newTrianglesList.Add(CreateTriangle(newVerticesEdges[item.Edges[1].PositionInEdgeList],item.Vertices[2], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                    }
                    if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == true
                        && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == false
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == true)
                    {
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], newVerticesEdges[item.Edges[0].PositionInEdgeList], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                        newTrianglesList.Add(CreateTriangle(newVerticesEdges[item.Edges[0].PositionInEdgeList], item.Vertices[1], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                        newTrianglesList.Add(CreateTriangle(item.Vertices[1], item.Vertices[2], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                    }

                    if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == true
                        && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == true
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == false)
                    {
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], newVerticesEdges[item.Edges[0].PositionInEdgeList], newVerticesEdges[item.Edges[1].PositionInEdgeList], newEdgesList));
                        newTrianglesList.Add(CreateTriangle(newVerticesEdges[item.Edges[0].PositionInEdgeList],item.Vertices[1], newVerticesEdges[item.Edges[1].PositionInEdgeList],newEdgesList));
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], newVerticesEdges[item.Edges[1].PositionInEdgeList], item.Vertices[2], newEdgesList));
                    }

                    if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == false
                        && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == true
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == false)
                    {
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], item.Vertices[1], newVerticesEdges[item.Edges[1].PositionInEdgeList], newEdgesList));
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0],newVerticesEdges[item.Edges[1].PositionInEdgeList], item.Vertices[3], newEdgesList));                        
                    }

                    if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == false
                        && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == false
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == true)
                    {
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], item.Vertices[1], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                        newTrianglesList.Add(CreateTriangle(item.Vertices[1], item.Vertices[2], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                    }

                    if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == true
                        && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == false
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == false)
                    {
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], newVerticesEdges[item.Edges[0].PositionInEdgeList],item.Vertices[2],newEdgesList));
                        newTrianglesList.Add(CreateTriangle(newVerticesEdges[item.Edges[0].PositionInEdgeList] , item.Vertices[1], item.Vertices[2], newEdgesList));
                    }

                    if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == false
                    && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == false
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == false)
                    {
                        newTrianglesList.Add(CreateTriangle(item.Vertices[0], item.Vertices[1], item.Vertices[2], newEdgesList));
                    }                
                }                

                if (edgeCanBeTesselated[item.Edges[0].PositionInEdgeList] == true
                    && edgeCanBeTesselated[item.Edges[1].PositionInEdgeList] == true
                    && edgeCanBeTesselated[item.Edges[2].PositionInEdgeList] == true)
                {
                    newTrianglesList.Add(CreateTriangle(item.Vertices[0], newVerticesEdges[item.Edges[0].PositionInEdgeList], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                    newTrianglesList.Add(CreateTriangle(newVerticesEdges[item.Edges[0].PositionInEdgeList], item.Vertices[1], newVerticesEdges[item.Edges[1].PositionInEdgeList], newEdgesList));
                    newTrianglesList.Add(CreateTriangle(newVerticesEdges[item.Edges[1].PositionInEdgeList], item.Vertices[2], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                    newTrianglesList.Add(CreateTriangle(newVerticesEdges[item.Edges[0].PositionInEdgeList], newVerticesEdges[item.Edges[1].PositionInEdgeList], newVerticesEdges[item.Edges[2].PositionInEdgeList], newEdgesList));
                }
                
            }
            triangs = newTrianglesList;
            edges = newEdgesList;
        }

        Triangulo CreateTriangle(Vertice v0, Vertice v1, Vertice v2 , List<Edge> edgeList)
        {            
            Triangulo newTriangulo = new Triangulo(v0, v1, v2);
            v0.addTriangulo(newTriangulo);
            v1.addTriangulo(newTriangulo);
            v2.addTriangulo(newTriangulo);
            
            Edge[] tempedges = new Edge[] { new Edge(v0, v1), new Edge(v1, v2), new Edge(v2, v0) };

            for (int j = 0; j < 3; j++)
            {
                ///LENTO Q DOI, BUT I DID NOT FIND AN EASY WAY yet
                Edge edx = edgeList.Find((a) => a.Vertices[0] == tempedges[j].Vertices[1] && a.Vertices[1] == tempedges[j].Vertices[0] || a.Vertices[0] == tempedges[j].Vertices[0] && a.Vertices[1] == tempedges[j].Vertices[1]);                
                if (edx == null)
                {
                    tempedges[j].TrianguloPertencente.Add(newTriangulo);
                    tempedges[j].PositionInEdgeList = edgeList.Count;
                    edgeList.Add(tempedges[j]);
                    newTriangulo.Edges[j] = tempedges[j];
                    tempedges[j].NotifyVerticesAboutEdge();
                }
                else
                {
                    edx.TrianguloPertencente.Add(newTriangulo);
                    newTriangulo.Edges[j] = edx;
                }
            }
            return newTriangulo;   
        }

    // This is a lookup table of the blending values for vertices 
    // [NUMBER OF EDGES][CURRENT EDGE] 
    void InitializeBlendingWeights()
    {              
       vertBlendingWeights = new float[MAX_VERTEX_VALENCE + 1][];
       for (int i = 0; i < MAX_VERTEX_VALENCE + 1; i++)
       {
           vertBlendingWeights[i] = new float[MAX_VERTEX_VALENCE + 1];
       }
      vertBlendingWeights[3][0] = 5.0f / 12.0f;
      vertBlendingWeights[3][1] = -1.0f / 12.0f;
      vertBlendingWeights[3][2] = -1.0f / 12.0f;
      vertBlendingWeights[4][0] = 3.0f / 8.0f;
      vertBlendingWeights[4][1] = 0.0f;
      vertBlendingWeights[4][2] = -1.0f / 8.0f;
      vertBlendingWeights[4][3] = 0.0f;
    
      for(int K=5; K<=MAX_VERTEX_VALENCE; K++)
      {
        for (int j=0; j<K; j++)
        {
            vertBlendingWeights[K][j] = (0.25f + (float)Math.Cos(2 * Math.PI * j / (float)K) + 0.5f * (float)Math.Cos(4 * Math.PI * j / (float)K)) / (float)K;         
        }
      }
      vertBlendingWeights[6][0] = (1.0f / 2.0f) - w;
      vertBlendingWeights[6][1] = (1.0f / 16.0f) + w;
      vertBlendingWeights[6][2] = (-1.0f / 16.0f) - w;
      vertBlendingWeights[6][3] = w;
      vertBlendingWeights[6][4] = (-1.0f / 16.0f) - w;
      vertBlendingWeights[6][5] = (1.0f / 16.0f) + w;
    }

    }
}
